//=============================================================================
///	Symbol
//=============================================================================

#include "senv.h"
#include "shash.h"
#include "sutil.h"
#include "smem.h"
#include "seval.h"
#include <assert.h>

static SExp s_global_env;
static SExp s_env_stack;


static SExp find_bind(SExp sym) {
	SExp p;
	for (p = s_env_stack; !nilp(p); p = cdr(p)) {
		SExp env = car(p);
		SExp s = assoc(sym, env);
		if (!nilp(s)) {
			return s;
		}
	}
	return nil;
}

static void set_global_var(SExp sym, SExp val) {
	sethash(sym, &s_global_env.ptr->hash, val);
}


void init_env(void) {
	s_global_env = make_hash_table();
	s_env_stack = nil;
}

void term_env(void) {
	s_global_env = nil;
	s_env_stack = nil;
}

SExp make_empty_env(void) {
	return nil;
}

static SExp add_bind(SExp env, SExp sym, SExp val) {
	SExp r;
	SExp s;
	push_stack(val);
	s = cons(sym, val);
	push_stack(s);
	r = cons(s, env);
	pop_stack(2);
	return r;
}

static SExp bind_arglist(SExp arglist, SExp values[], int n) {
	SExp env = make_empty_env();
	SExp p;
	int i;
	for (p = arglist, i=0; !nilp(p); p = cdr(p), ++i) {
		if (consp(p)) {
			SExp sym = car(p);
			assert(type_of(sym) == tSymbol);
			push_stack(env);
			env = add_bind(env, sym, i < n ? values[i] : nil);
			pop_stack(1);
		} else {
			SExp sym = p;
			assert(type_of(sym) == tSymbol);
			push_stack(env);
			env = add_bind(env, sym, i < n ? array2s(&values[i], n-i) : nil);
			pop_stack(1);
			break;
		}
	}
	return env;
}

void extend_env(SExp arglist, SExp values[], int n) {
	SExp newenv = bind_arglist(arglist, values, n);
	push_env(newenv);
}

void setq(SExp sym, SExp val) {
	SExp p = find_bind(sym);
	if (!nilp(p)) {
		rplacd(p, val);
	} else {
		set_global_var(sym, val);
	}
}

void defconst(SExp sym, SExp val) {
	// @fixme
	setq(sym, val);
}

SExp global_variable_ref(SExp sym) {
	return gethash(sym, &s_global_env.ptr->hash);
}

SExp getvar(SExp sym) {
	SExp s = find_bind(sym);
	if (!nilp(s)) {
		return cdr(s);
	} else {
		return global_variable_ref(sym);
	}
}



void push_env(SExp newenv) {
	push_stack(newenv);
	s_env_stack = cons(newenv, s_env_stack);
	pop_stack(1);
}

void pop_env(void) {
	s_env_stack = cdr(s_env_stack);
}

SExp get_current_env(void) {
	return s_env_stack;
}

void set_env(SExp env) {
	s_env_stack = env;
}


void reset_env_to_top(void) {
	s_env_stack = nil;
}


void mark_env(void) {
	SMARK(s_global_env);
	SMARK(s_env_stack);
}
